################################################################################
#  Copyright (c) by Medtronic, Inc., 2013.  
#  $HeadURL: http://build-tool.googlecode.com/svn/trunk/BuildTool/hexcrc.pl $
#  $Revision: 71 $
#  $Date: 2013-08-12 13:43:22 +0000 (Mon, 12 Aug 2013) $
################################################################################

use strict;
use warnings;

use Env qw(BUILD_TOOLS_PATH);
use lib $BUILD_TOOLS_PATH;

use hextools qw(Init_Log Log Get_Timestamp Parse_Intel_Hex_File Generate_Hex_File Calculate_CRC);

##########################################################################################
#
#  PURPOSE
#
#    This script calculates a CCITT-CRC value from an Intel-32 hex file, and stores the
#    CRC a specified address in the output hex file. It also performs other services such
#    as filling unused addresses.
#
#
#  SYNOPSIS
#
#    Usage:  hexcrc <arguments>
#
#    Arguments:
#
#            -a <start_addr>: The starting address for the FW image.
#
#            -b <filename>:   The name of a file that contains the FW build revision.
#                             The revision file should contain two numbers, with optional
#                             text labels. For example:
#                                 major = 12
#                                 minor = 3
#                             If this argument is omitted, no build revision will be
#                             inserted in the output hex file. If this argument is
#                             provided, then the -r argument must also be provided.
#
#            -c <crc_addr>:   The address at which the CRC should be written. This argument
#                             is mutually exlusive with the -e argument. If both the -c
#                             and -e arguments are omitted, no CRC will be generated. 
#
#            -e <end_addr>:   The hex address at which the end address of the code (i.e., the
#                             address of the first unused byte) should be written. If this
#                             argument is provided, the CRC will be written at the end address.
#                             This argument is mutually exlusive with the -e argument. If both
#                             the -c and -e arguments are omitted, no CRC will be generated. 
#
#            -f <fill_value>: If this argument is provided, unused addresses in the memory
#                             space will be filled with the given value. The fill value
#                             should be a 1-byte hex value (e.g., "0xFF"). If this argument
#                             is provided, the CRC will include the entire memory space
#                             rather than just the portion that's occupied by code.
#
#            -i <filename>:   The name of the input Intel hex format file.
#
#            -l <filename>:   The name of the log file. If this argument is omitted,
#                             no log file will be generated.
#
#            -o <filename>:   The name of the output hex file.
#
#            -r <rev_addr>:   The address at which the firmware revision number should
#                             be written. If the -r argument is provided, then the -b
#                             argument must also be provided.
#
#            -s <mem_size>:   The size (number of bytes), in hex, of the memory space.
#
#    Examples:
#
#        This example is for the application code in flash on the Galaxy platform. In this
#        example, unused addresses in the memory space are not filled, the address of the
#        last code byte is stored at the specified location, and the CRC is stored after
#        the the last code byte.
#
#            hexcrc -a 0x10000000 -e 0x10000448 -s 0x80000 -i main_code.intel.hex -o main_code_crced.hex -b build.txt -r 0x10000440 
#
#        This example is for the trim table in the UserConfig flash sector on the Galaxy
#        platform. In this example, unused addresses in the memory space are filled and the
#        CRC is stored at the end of the memory space.
#
#            hexcrc -a 0x10084000 -c 0x100847FE -s 0x800 -f 0xFF -i trim.intel.hex -o trim_RTL.hex
#
#        This example is for the flex table in the UserConfig flash sector on the Galaxy
#        platform. In this example, unused addresses in the memory space are filled and the
#        CRC is placed at the end of the memory space.:
#
#            hexcrc -a 0x10084800 -c 0x10084FFE -s 0x800 -f 0xFF -i flex.intel.hex -o flex_RTL.hex
#
#        This example is for the bootup/backup code in ROM on the Galaxy platform. In this
#        example, unused addresses in the memory space are not filled, the address of the
#        last code byte is stored at the specified location, and the CRC is stored after
#        the the last code byte.
#
#            hexcrc -a 0x01000000 -e 0x01000448 -s 0x4000 -i bc_code.intel.hex -o bc_code_crced.hex -b build.txt -r 0x01000440 
#
#
#  DESCRIPTION
#
#    This script calculates a CCITT-CRC value for an Intel-32 hex file, and stores the
#    CRC in the output hex file at the specified location. When storing the CRC, it
#    swaps the MSB and LSB so that when verifying the CRC it will always be 0. It also
#    pads out the file, if necessary, to ensure that it ends on a 32-bit boundary
#    (this is done because the flash ECC operates on 32-bit words, which means the
#    smallest programming size is a word).
#
#    If the -e argument is provided, the script will also determine the address of the
#    last code byte in the input file, and will write this address (plus one) to the
#    location specified by <crc_addr>. This allows the CRC to cover only the range in
#    which code resides, allowing the remaining space to remain in an erased state
#    (for flash).
#
#    For generating the hex file, the script assumes that there are no unused addresses
#    in the code space. Any unfilled addresses (between the lowest and  highest addresses
#    in the input file) will be flagged as an error.
#
##########################################################################################


#--------------------------------------------------------------------------------------
#
# Initialize constants and global variables.
#
#--------------------------------------------------------------------------------------

my $start_address;
my $build_rev_name;
my $rev_address;
my $crc_address;
my $end_address;
my $crc_indirection;
my $arg_c_provided;
my $arg_e_provided;
my $fill_value;
my $memory_size;
my $infile_name;
my $logfile_name;
my $outfile_name;
my $infile;   # File handle
my $outfile;  # File handle

# The memory array holds the code bytes that are extracted from the Intel hex file.
# This array is used for calculating the code CRC and for generating the output hex
# file. To keep the size of the array to a manageable size, it's necessary to
# subtract the starting address of the code (e.g., 0x01000000 for ROM or 0x10000000
# for flash) when calculating the array index.
my @memory_array;


#--------------------------------------------------------------------------------------
#
# Process command line arguments
#
#--------------------------------------------------------------------------------------
while (@ARGV and $ARGV[0] =~ /^-/)
{
   $_ = shift;
   if (/^-a/)
   {
      $start_address = hex(shift);
   }
   elsif (/^-b/)
   {
      $build_rev_name = shift;
   }
   elsif (/^-c/)
   {
      $arg_c_provided = 1;
      $crc_indirection = 0;  # CRC goes at end of memory space
      $crc_address = hex(shift);
   }
   elsif (/^-e/)
   {
      $arg_e_provided = 1;
      $crc_indirection = 1;  # CRC goes at end of code
      $crc_address = hex(shift);
   }
   elsif (/^-f/)
   {
      $fill_value = hex(shift);
      chomp $fill_value;
   }
   elsif (/^-i/)
   {
      $infile_name = shift;
   }
   elsif (/^-l/)
   {
      $logfile_name = shift;
   }
   elsif (/^-o/)
   {
      $outfile_name = shift;
   }
   elsif (/^-r/)
   {
      $rev_address = hex(shift);
   }
   elsif (/^-s/)
   {
      $memory_size = hex(shift);
      if (($memory_size % 4) != 0) {
          print "Memory size must be a multiple of 4\n";
          exit;
      }
   }
   else
   {
      print "Unrecognized switch: $_\n";
      exit;
   }
}

# Check for extraneous arguments
if (defined($ARGV[0]))
{
   print "Invalid argument(s): @ARGV\n";
   exit;
}

# Check for missing arguments
die "No start address provided\n"            unless $start_address;
die "No memory size provided\n"              unless $memory_size;
die "No input file name provided\n"          unless $infile_name;
die "No output file name provided\n"         unless $outfile_name;
if ($build_rev_name && !$rev_address) {
   die "if -b argument is provided then -r argument must also be provided\n";
}
if ($rev_address && !$build_rev_name) {
   die "if -r argument is provided then -b argument must also be provided\n";
}

# Check for invalid arguments
if ($crc_address) {
   if (($crc_address < $start_address) || ($crc_address >= ($start_address + $memory_size))) {
       die "CRC address is out of range\n";
   }
}
if ($rev_address) {
   if (($rev_address < $start_address) || ($rev_address >= ($start_address + $memory_size))) {
       die "Revision address is out of range\n";
   }
}
if ($arg_c_provided && $arg_e_provided) {
    die "-c and -e are mutually exlusive arguments\n";
}

#--------------------------------------------------------------------------------------
# 
# Initialize input and output files. Write header information to the log file.
#
#--------------------------------------------------------------------------------------

# Initialize the log file (if the file name is null, no log file will be generated).
Init_Log($logfile_name);

# Open the input and output hex files. Lexical filehandles are used so that they can be
# passed as arguments to the hextools subroutines.
open($infile,  '<', $infile_name)  or die "Can't open $infile_name: $!\n";
open($outfile, '>', $outfile_name) or die "Can't open $outfile_name: $!\n";

my $timestamp = Get_Timestamp();

my $svn_revision_string = '\$Revision: 71 $';  # Get file revision via SVN keyword
my $revision;
if ($svn_revision_string =~ /Revision:\s+(\d+)/)  
{
   $revision = "Revision $1";
}

my $MajorRev = 0;
my $MinorRev = 0;
if (defined $build_rev_name) {
   if (open (REV_FILE, "<$build_rev_name")) {
      # TBD check values for validity?

      $MajorRev = <REV_FILE>;
      chomp $MajorRev;
      $MajorRev =~ s/.+?(\d+).*/$1/;

      $MinorRev = <REV_FILE>;
      chomp $MinorRev;
      $MinorRev =~ s/.+?(\d+).*/$1/;

      close REV_FILE;
   }
   else {
       Log("WARNING: Build Revision file \"$build_rev_name\" could not be opened!\n\n");
   }
}

Log(sprintf "hexcrc:                  $revision\n");
Log(sprintf "Run rime:                $timestamp\n");
Log(sprintf "Input hex file:          $infile_name\n");
Log(sprintf "Output hex file:         $outfile_name\n");
if (defined $build_rev_name) {
   Log(sprintf "Build revision file:     $build_rev_name\n");
   Log(sprintf "Firmware revision:       $MajorRev.$MinorRev\n");
   Log(sprintf "FW revision address:     0x%08X\n", $rev_address);
}
if (defined $crc_address) {
   if ($crc_indirection) {
      Log(sprintf "Location of CRC address: 0x%08X\n", $crc_address);
   }
   else {
      Log(sprintf "CRC address:             0x%08X\n", $crc_address);
   }
}
if (defined $fill_value) {
   Log(sprintf "Fill Value:              0x%02X\n", $fill_value);
}
Log(sprintf "Memory size:             0x%05X\n", $memory_size);
Log(sprintf "Start address:           0x%08X\n", $start_address);


#--------------------------------------------------------------------------------------
# 
# Parse the input file. The data from the file will be placed in an array, indexed
# by address.
#
#--------------------------------------------------------------------------------------
$end_address = Parse_Intel_Hex_File($infile, \@memory_array, $start_address, $memory_size);
Log(sprintf "Max code address:        0x%05X\n", $end_address);


#--------------------------------------------------------------------------------------
# 
# If a fill value was provided, fill unused addresses.
#
#--------------------------------------------------------------------------------------
if (defined $fill_value) {
   for (my $index = $end_address + 1; $index < ($start_address + $memory_size); $index++) {
      $memory_array[$index - $start_address] = sprintf "%02X", $fill_value;
   }
}


#--------------------------------------------------------------------------------------
# 
# Add any additional information (CRC, revision number, padding, etc.) to the
# memory array.
#
#--------------------------------------------------------------------------------------

# If a CRC address was provided (via either the -c or -e argument), calculate the CRC
# and store it in the memory array. The CRC is stored LSB followed by MSB, so when a
# CRC check of the entire image is performed, the CRC is 0.
if (defined $crc_address) {
   my $calculated_crc;

   # If a fill value was provided, then the CRC will cover the entire memory space.
   # If no fill value was provided, then the CRC will cover only the memory that 
   # contains code.
   if (defined $fill_value) {
      $calculated_crc = Calculate_CRC(\@memory_array, $start_address, ($start_address + $memory_size - 1));
      Log(sprintf "Calculated CRC:          %04X\n", $calculated_crc);
   }
   else {
      $calculated_crc = Calculate_CRC(\@memory_array, $start_address, $end_address);
      Log(sprintf "Calculated CRC:          %04X\n", $calculated_crc);
   }

   # If the -c argument was provided (i.e., $crc_indirection == 0), then the CRC is stored
   # at the given address. If the -e argument was provided (i.e., $crc_indirection == 1),
   # then the address of the last code byte is stored at the given address, and the CRC is
   # written to the 2 bytes immediately following the last code byte.
   if ($crc_indirection) {
      # Store the max address (plus one) at the specified location.
      $memory_array[$crc_address + 3 - $start_address] = sprintf "%02X", ($end_address + 1) / 0x1000000;  #MSB
      $memory_array[$crc_address + 2 - $start_address] = sprintf "%02X", ($end_address + 1) / 0x10000 % 0x100;
      $memory_array[$crc_address + 1 - $start_address] = sprintf "%02X", ($end_address + 1) / 0x100 % 0x100;
      $memory_array[$crc_address     - $start_address] = sprintf "%02X", ($end_address + 1) % 0x100;  #LSB

      # Store the calculated CRC at the end of the FW image. Increase end_address to account for CRC.
      $end_address++;
      $memory_array[$end_address - $start_address] = sprintf "%02X", $calculated_crc % 256;  # LSB
      $end_address++;
      $memory_array[$end_address - $start_address] = sprintf "%02X", $calculated_crc / 256;  # MSB
   }
   else {
      # Store the calculated CRC at the given address.
      $memory_array[$crc_address - $start_address]     = sprintf "%02X", $calculated_crc % 256;  # LSB
      $memory_array[$crc_address - $start_address + 1] = sprintf "%02X", $calculated_crc / 256;  # MSB
   }
}

# Store the firmware revision numbers at the given address location.
if (defined $rev_address) {
   $memory_array[$rev_address     - $start_address] = sprintf "%02X", $MajorRev;
   $memory_array[$rev_address + 1 - $start_address] = sprintf "%02X", $MinorRev;
}

# If the memory space was not filled, pad the FW image out, if necessary, so it ends on
# a word boundary.
if (!defined $fill_value) {
   my $pad_count = ($end_address + 1) % 4;
   for (my $i=0; $i < $pad_count; $i++) {
      $end_address++;
      $memory_array[$end_address - $start_address] = sprintf "%02X", 0;
   }
}


#--------------------------------------------------------------------------------------
# 
# Generate the hex output file.
#
#--------------------------------------------------------------------------------------
if (defined $fill_value) {
   Generate_Hex_File($outfile, \@memory_array, $start_address, ($start_address + $memory_size - 1));
}
else {
   Generate_Hex_File($outfile, \@memory_array, $start_address, $end_address);
}


#--------------------------------------------------------------------------------------
# 
# Close the input and output files.
#
#--------------------------------------------------------------------------------------
close($infile);
close($outfile);
